﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  NotificationRuleItem.cs
//    
// Abstract:
//
//  This class represents an individual Enhanced Reminder rule.
//  
//--

using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ComponentModel;

using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders
{
    /// <summary>
    /// Class defining a notification rule item.
    /// </summary>
    [Serializable()]
    public sealed class NotificationRuleItem : INotifyPropertyChanged
    {
        /// <summary>
        /// Format of the rule description string logged by the scheduler.
        /// </summary>
        public const string FormatString = "Rule name={0}, enabled={1}, type={2}, timestamp={3}, conditions={4}, NotifyChannels={5}, JoinNowChannels={6}";

        #region Private members

        /// <summary>
        /// Flag to indicate if this rule is selected
        /// </summary>
        private bool _isEnabled;

        /// <summary>
        /// Which object the rule applies to
        /// </summary>
        private MailboxType _mailboxType;

        /// <summary>
        /// List of conditions
        /// </summary>
        private Collection<NotificationCondition> _conditions = new Collection<NotificationCondition>();

        /// <summary>
        /// List of channels to use for notification attempts
        /// </summary>
        private NotificationChannelListAbsoluteTime _notificationChannels;

        /// <summary>
        /// List of channels for join now
        /// </summary>
        private NotificationChannelListLeadTime _joinNowChannels;

        /// <summary>
        /// Rule name
        /// </summary>
        private string _ruleName;

        /// <summary>
        /// Timestamp (in UTC) for last modification. The scheduler needs 
        /// this because because it only wants the rule to apply to non-calendar 
        /// messages that arrive after the rule was created
        /// </summary>
        private DateTime _timestamp;

        /// <summary>
        /// Unique id identifying the rule 
        /// </summary>
        private Guid _id;
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="mailboxType">MailboxType enum</param>
        /// <param name="ruleName">Rule name - can be an empty when creating a new rule</param>
        /// <param name="timestamp">Timestamp in UTC</param>
        /// <param name="channels">The channels to attempt to deliver a notification on.</param>
        public NotificationRuleItem(MailboxType mailboxType, string ruleName, DateTime timestamp, IEnumerable<NotificationChannel> channels)
        {
            Debug.Assert(ruleName != null, "ruleName != null");

            _mailboxType = mailboxType;
            _ruleName = ruleName;
            _timestamp = timestamp;

            if (channels != null)
            {
                _notificationChannels = new NotificationChannelListAbsoluteTime(channels);
                _joinNowChannels = new NotificationChannelListLeadTime(channels);
            }

            // Generate a new id
            _id = Guid.NewGuid();

            // Add conditions
            _conditions.Add(new NotificationTimeCondition());
            _conditions.Add(new NotificationPeopleCondition());
            _conditions.Add(new NotificationStatusCondition());
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="rule">Rule to be copied.</param>
        public NotificationRuleItem(NotificationRuleItem rule)
        {
            Debug.Assert(rule != null, "rule");
            Debug.Assert(rule.Conditions != null, "rule.Conditions");

            _isEnabled = rule._isEnabled;
            _mailboxType = rule._mailboxType;
            _ruleName = rule._ruleName;
            _timestamp = rule._timestamp;
            _id = rule._id;

            if (rule._notificationChannels != null)
            {
                _notificationChannels = new NotificationChannelListAbsoluteTime(rule._notificationChannels);
            }

            if (rule._joinNowChannels != null)
            {
                _joinNowChannels = new NotificationChannelListLeadTime(rule._joinNowChannels);
            }

            _conditions = new Collection<NotificationCondition>();

            foreach (NotificationCondition condition in rule.Conditions)
            {
                // A Copy() method may have been better than a copy constructor for NotificationCondition
                // as here we could just do _conditions.Add(condition.Copy()).  
                NotificationTimeCondition timeCondition = condition as NotificationTimeCondition;
                NotificationPeopleCondition peopleCondition = condition as NotificationPeopleCondition;
                NotificationStatusCondition statusCondition = condition as NotificationStatusCondition;
                
                if (timeCondition != null)
                {
                    _conditions.Add(new NotificationTimeCondition(timeCondition));
                }
                else if (peopleCondition != null)
                {
                    _conditions.Add(new NotificationPeopleCondition(peopleCondition));
                }
                else if (statusCondition != null)
                {
                    _conditions.Add(new NotificationStatusCondition(statusCondition));
                }
                else
                {
                    Debug.Fail("Unexpected condition type");
                }
            }

            Debug.Assert(_conditions.Count == rule.Conditions.Count, "Unexpected number of conditions");
        }

        /// <summary>
        /// Sync the rule's reference channels for notifications and join now to the 
        /// master channel list.
        /// </summary>
        /// <param name="masterChannels">The master channel list.</param>
        /// <param name="appendUnseenChannels">True to append channels to the end of the reference list; false otherwise.</param>
        public void SyncToMasterChannelsList(IEnumerable<NotificationChannel> masterChannels, bool appendUnseenChannels)
        {
            if (_notificationChannels != null)
            {
                _notificationChannels.SyncToMasterChannelsList(masterChannels, false, appendUnseenChannels);
            }
            else
            {
                _notificationChannels = new NotificationChannelListAbsoluteTime(masterChannels);
            }

            if (_joinNowChannels != null)
            {
                _joinNowChannels.SyncToMasterChannelsList(masterChannels, true, appendUnseenChannels);
            }
            else
            {
                _joinNowChannels = new NotificationChannelListLeadTime(masterChannels);
            }
        }
 
        #region Public properties

        /// <summary>
        /// Which object the rule applies to
        /// </summary>
        public MailboxType MailboxType
        {
            get
            {
                return _mailboxType;
            }
            set
            {
                _mailboxType = value;
            }
        }

        /// <summary>
        /// List of senders
        /// </summary>
        public Collection<NotificationCondition> Conditions
        {
            get
            {
                return _conditions;
            }
        }

        /// <summary>
        /// Name for the rule
        /// </summary>
        public string RuleName
        {
            get
            {
                return _ruleName;
            }
            set
            {
                _ruleName = value;
            }
        }

        /// <summary>
        /// Time of last modification in UTC
        /// </summary>
        public DateTime Timestamp
        {
            get
            {
                return _timestamp;
            }
            set
            {
                _timestamp = value;
            }
        }

        /// <summary>
        /// Unique id identifying rule
        /// </summary>
        public Guid Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
            }
        }

        /// <summary>
        /// Flag to indicate if this rule is selected
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return _isEnabled;
            }
            set
            {
                _isEnabled = value;
                FirePropertyChangedEvent("IsEnabled");
            }
        }

        /// <summary>
        /// The list of notification channels to use when delivering a rule.
        /// Indicates that this rule is enabled for notifications.
        /// </summary>
        public bool IsNotificationEnabled
        {
            get
            {
                return _notificationChannels != null && _notificationChannels.IsEnabled;
            }
        }

        /// <summary>
        /// Indicates that this rule is enabled for JoinNow.
        /// </summary>
        public bool IsJoinNowEnabled
        {
            get
            {
                return _joinNowChannels != null && _joinNowChannels.IsEnabled;
            }
        }

        /// <summary>
        /// The list of notification channels to use when delivering a rule.
        /// </summary>
        public NotificationChannelListAbsoluteTime NotificationChannels
        {
            get
            {
                return _notificationChannels;
            }
            set
            {
                Debug.Assert(value != null, "value");
                _notificationChannels = value;
            }
        }

        /// <summary>
        /// The list of JoinNow channels to use when delivering a rule.
        /// </summary>
        public NotificationChannelListLeadTime JoinNowChannels
        {
            get
            {
                return _joinNowChannels;
            }
            set
            {
                Debug.Assert(value != null, "value");
                _joinNowChannels = value;
            }
        }

        #endregion

        /// <summary>
        /// Get condition based on the type
        /// </summary>
        /// <param name="conditionType">The condition type.</param>
        /// <returns>The condition matching the target type.</returns>
        public NotificationCondition GetCondition(Type conditionType)
        {
            Debug.Assert(conditionType == typeof(NotificationTimeCondition) || conditionType == typeof(NotificationStatusCondition) || conditionType == typeof(NotificationPeopleCondition),
                "type == typeof(NotificationTimeCondition) || type == typeof(NotificationStatusCondition) || type == typeof(NotificationPeopleCondition)");

            foreach (NotificationCondition condition in Conditions)
            {
                if (condition.GetType() == conditionType)
                {
                    return condition;
                }
            }

            Debug.Fail("An expected NotificationCondition is not found");
            return null;
        }

        /// <summary>
        /// Determines if this rule matches the specified conditions.
        /// </summary>
        /// <param name="notificationData">Conditions</param>
        /// <param name="containsStatusCondition">Indicates if this rule contains any selected status-based conditions</param>
        /// <returns>
        /// true if the rule matches the conditions; false otherwise.
        /// </returns>
        public bool IsSatisfied(INotificationData notificationData, out bool containsStatusCondition)
        {
            Debug.Assert(notificationData != null, "notificationData != null");
            
            containsStatusCondition = false;

            // Check that all selected conditions are satisfied
            foreach (NotificationCondition condition in _conditions)
            {
                if (condition.IsSelected)
                {
                    if (!condition.IsSatisfied(notificationData))
                    {
                        return false;
                    }

                    if (condition is NotificationStatusCondition)
                    {
                        // Can't check presence now, so defer that check until the point of delivery
                        containsStatusCondition = true;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Creates a ReminderItem from the specified rules.
        /// </summary>
        /// <param name="reminderRule">Reminder rule</param>
        /// <param name="joinNowRule">JoinNow rule</param>
        /// <param name="timeZoneInfo">Timezone for absolute time reminders</param>
        /// <returns>
        /// The ReminderItem.
        /// </returns>
        public static ReminderItem CreateCalendarReminderItem(NotificationRuleItem reminderRule, NotificationRuleItem joinNowRule, TimeZoneInfo timeZoneInfo)
        {
            Debug.Assert(reminderRule != null || joinNowRule != null, "reminderRule != null || joinNowRule != null");

            return new ReminderItem(reminderRule != null ? reminderRule._notificationChannels : null,
                joinNowRule != null ? joinNowRule._joinNowChannels : null, timeZoneInfo);
        }

        /// <summary>
        /// Creates an array of reminders; one for each rule in the specified array of rules.
        /// </summary>
        /// <param name="rules">Rules</param>
        /// <returns>
        /// Array of reminders.
        /// </returns>
        public static ReminderItem[] CreateReminderItems(NotificationRuleItem[] rules)
        {
            Debug.Assert(rules != null, "rules != null");
            Debug.Assert(rules.Length > 0, "rules.Length > 0");

            ReminderItem[] reminderItems = new ReminderItem[rules.Length];

            for (int i = 0; i < rules.Length; i++)
            {
                reminderItems[i] = rules[i].CreateReminderItem();
            }

            return reminderItems;
        }

        /// <summary>
        /// Determines if this rule equals the specified rule.
        /// </summary>
        /// <param name="rule">Rule to compare against</param>
        /// <returns>
        /// true if the rules are equivalent; false otherwise
        /// </returns>
        public bool Equals(NotificationRuleItem rule)
        {
            Debug.Assert(rule != null, "rule != null");
            Debug.Assert(rule._conditions != null, "rule._conditions != null");

            if (_isEnabled != rule._isEnabled)
            {
                return false;
            }

            if (_mailboxType != rule._mailboxType)
            {
                return false;
            }

            if (_conditions.Count != rule._conditions.Count)
            {
                return false;
            }

            for (int i = 0; i < _conditions.Count; i++)
            {
                if (!_conditions[i].Equals(rule._conditions[i]))
                {
                    return false;
                }
            }

            if (_notificationChannels != null && rule._notificationChannels != null)
            {
                if (!_notificationChannels.Equals(rule._notificationChannels))
                {
                    return false;
                }
            }
            else
            {
                if (_notificationChannels != null || rule._notificationChannels != null)
                {
                    return false;
                }
            }

            if (_joinNowChannels != null && rule._joinNowChannels != null)
            {
                if (!_joinNowChannels.Equals(rule._joinNowChannels))
                {
                    return false;
                }
            }
            else
            {
                if (_joinNowChannels != null || rule._joinNowChannels != null)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Creates a ReminderItem from this rule.
        /// </summary>
        /// <returns>
        /// The ReminderItem.
        /// </returns>
        private ReminderItem CreateReminderItem()
        {
            // Create the reminder from the two sets of channels
            ReminderItem reminderItem = new ReminderItem(_notificationChannels, _joinNowChannels, null /* timeZoneInfo */);

            // Update the presence status
            foreach (NotificationCondition condition in _conditions)
            {
                NotificationStatusCondition statusCondition = condition as NotificationStatusCondition;

                if (statusCondition != null && statusCondition.IsSelected)
                {
                    reminderItem.Presence |= statusCondition.Presence;
                }
            }

            return reminderItem;
        }

        /// <summary>
        /// Log the rule info to the verbose trace logs.
        /// </summary>
        /// <param name="context">The logging context.</param>
        internal void LogTraceInfo(Context context)
        {
            Debug.Assert(context != null, "context");
            Debug.Assert(_conditions != null, "_conditions");

            StringBuilder conditionList = new StringBuilder();
            foreach (NotificationCondition condition in Conditions)
            {
                 if (condition.IsSelected)           
                 {
                     if (conditionList.Length > 0)
                     {                     
                         conditionList.Append('+'); // Multiple conditions.   
                     }
                     if (condition is NotificationTimeCondition)
                     {
                         conditionList.Append("Time");
                     }
                     else if (condition is NotificationPeopleCondition)
                     {
                         conditionList.Append("Sender");
                     }
                     else
                     {
                         Debug.Assert(condition is NotificationStatusCondition, "Unexpected condition type");
                         conditionList.Append("Presence");
                     }
                 }                   
            }

            Logger.WriteTraceVerbose(context,
                                     FormatString,
                                     RuleName,
                                     IsEnabled,
                                     MailboxType,
                                     Timestamp,
                                     conditionList,
                                     _notificationChannels == null ? String.Empty : _notificationChannels.GetLogString(),
                                     _joinNowChannels == null ? String.Empty : _joinNowChannels.GetLogString());
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fires the property changed event.
        /// </summary>
        /// <param name="name"></param>
        private void FirePropertyChangedEvent(string name)
        {
            Debug.Assert(!String.IsNullOrEmpty(name), "name");

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        /// <summary>
        /// The property changed event.
        /// </summary>
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
